<?php

/*******************************************************************************
 *
 * CLASS [ debug ]
 *
 *******************************************************************************
 *
 * Deboguage de classes et d'applications
 *
 *******************************************************************************
 *
 * @package libs\debug
 * @version 20100830
 * @author Stanislas PERRIN <tanislas.perrin@gmail.com>
 * @author Johann PAVIS <johann.pavis@gmail.com> 
 *
 * @licence GNU/LGPL v3
 * 
 ******************************************************************************/

################################################################################
#
# Namespace
#
################################################################################
# namespace phutile\libs\charset; // uncomment only if php version >= 5.3

################################################################################
#
# Hook to handle errors as exceptions
#
################################################################################
function errorToException($errno, $errstr, $errfile, $errline )
{
	$e = new debug($errstr,$errno);
	$e->setFile = $errfile;
	$e->setLine = $errline;
	throw $e;
}
// If you don't want php error to be treated as exceptions, comment this line :
if ($_DEBUG_CATCH_PHP_ERRORS) set_error_handler("errorToException");

################################################################################
#
# CONFIGURATION EXAMPLE FOR « debug »
#
################################################################################

/*
 * Note : you must set this configuration variables *before* including the
 * debug.class.php file. It could be an included php file like debug.conf.php,
 * or set directly in your file, as you wish.
 * 
 * Q : Why use external vaiables and "globalize" them instead of creating class
 * properties and manage them with public methods ?
 * A : I wanted to keep the exception leverage as short and simple as possible :
 * when an exception is thrown, you just have to throw a new debug(), not
 * create an instance of debug(), call methods to set properties, throw the
 * newly created object and destroy it right after. « Global » variables are
 * the least bad answer to this problem, imho.
 * 
 * $_DEBUG_SENDMAIL = false;
 * $_DEBUG_WRITELOG = false;
 * $_DEBUG_SHOWERROR = false;
 * $_DEBUG_LOGPATH = './log';
 * $_DEBUG_APPNAME = 'test';
 * $_DEBUG_VERBOSE = 'VVV';
 * $_DEBUG_MAILFROM = 'appname@domain.com';
 * $_DEBUG_MAILTO = array('stanislas.perrin@gmail.com');
 * 
 * Activates SYSLOG on errors -- dunno what happens if "true" on Windows platforms...
 * $_DEBUG_SYSLOG = false;
 * 
 * Catch PHP errors as exceptions ?
 * $_DEBUG_CATCH_PHP_ERRORS = true;
 * 
 * 
 * Network parameters
 * $_BEGUG_NETWORK = true;
 * $_DEBUG_NETWORK_SOURCE_IP = '192.168.0.2'; // local IP
 * $_DEBUG_NETWORK_DESTINATION_IP = '192.168.0.254'; // distant IP
 * $_DEBUG_NETWORK_SOURCE_PORT = '800';
 * $_DEBUG_NETWORK_DESTINATION_PORT = '800';
 * 
 */

################################################################################
#
# Local Configuration in case you don't want to include an external file
#
################################################################################

$_DEBUG_SENDMAIL = false;
$_DEBUG_WRITELOG = false;
$_DEBUG_SHOWERROR = false;
$_DEBUG_LOGPATH = './log';
$_DEBUG_APPNAME = 'test';
$_DEBUG_VERBOSE = 'VVV';
$_DEBUG_MAILFROM = 'appname@domain.com';
$_DEBUG_MAILTO = array('stanislas.perrin@gmail.com');
 
// Activates SYSLOG on errors -- dunno what happens if "true" on Windows platforms...
$_DEBUG_SYSLOG = false;
 
// Catch PHP errors as exceptions ?
$_DEBUG_CATCH_PHP_ERRORS = true;

// Network parameters
$_BEGUG_NETWORK = true;
$_DEBUG_NETWORK_SOURCE_IP = '192.168.168.168'; // local IP
$_DEBUG_NETWORK_DESTINATION_IP = '192.168.168.254'; // distant IP
$_DEBUG_NETWORK_SOURCE_PORT = '800';
$_DEBUG_NETWORK_DESTINATION_PORT = '800';
 


################################################################################
#
# The famous and fabulous « debug » class !
#
################################################################################

class debug extends Exception
{

	############################################################################
	#
	# PROPERTIES
	#
	############################################################################
	
	protected $code = 0;
	protected $level = 0;
	protected $message = '';
	protected $file = '';
	protected $line = 0;
	
	// informations about client
	protected $client = array();
	
	// date & time
	protected $errorDate;
	protected $errorTime;
	
	// mail
	protected $mailSubject = '';
	protected $mailMessage = '';
	
	// misc
	private $_CRLF = "\r\n";
	
	// network
	protected $netSocket = null;
	protected $netSocketLastErrorCode = 0;
	protected $netSocketLastErrorMessage = null;
	protected $netSocketCreateRetry = 3;
	protected static $netSocketPacketCounter = 0;

	############################################################################
	#
	# CONSTRUCTOR
	#
	############################################################################
	
	/**
	 * 
	 * __construct()
	 * 
	 * @param string $message error message
	 * @param int $code error code (default 0)
	 * @param array array($code => $message) -- alternative method
	 * 
	 */
	public function __construct($message, $code=0)
	{   
		// Importation des variables de configuration
		global $_DEBUG_SENDMAIL, $_DEBUG_WRITELOG, $_DEBUG_SHOWERROR, $_DEBUG_SYSLOG, $_DEBUG_NETWORK;
		
		// could take an array as first parameter
		if (is_array($message))
		{
			// "$message"should be a one-element array, but more elegant to 
			// retrieve the « index => value » pair with a "foreach"
			// if ever $message got multpliple elements, only the last one
			// will be considered
			foreach($message as $c => $m)
			{
				$code = $c;
				$message = $m;
			}
		}
		
		parent::__construct($message, $code);
		$this->errorDate = date("d/m/Y");
		$this->errorTime = date("H:i:s");
		
		// Informations sur le client
		$this->getClientInformations();
		
		if ($_DEBUG_SENDMAIL)
		{
			$this->sendMail();
		}
		
		if ($_DEBUG_WRITELOG)
		{
			$this->logToFile();
		}

		if ($_DEBUG_SHOWERROR)
		{
			print($this->__toString());
		}

		if ($_DEBUG_SYSLOG)
		{
			$dumb = $this->setSyslog();
		}
		
		if ($_DEBUG_NETWORK)
		{
			$this->netSocketCreate();
			$this->netSocketSend($this->message);
			print_r($this);
		}
		
		
	}
 	
	/**
	 * 
	 * __destruct()
	 * 
	 */
	/*
	private function __destruct()
	{
		//
	}
	*/

	############################################################################
	#
	# PUBLIC METHODS
	#
	############################################################################
	
	/*
	* stopExec()
	* affiche la trace de l'erreur et stoppe l'execution du programme
	*/
	public function stopExec()
	{
		print($this->__toString());
		die();
	}
	
	public function logToFile()
	{
		// Importation des variables de configuration
		global $_DEBUG_LOGPATH, $_DEBUG_APPNAME;

		$logFileName = '';
		$dateStamp = date("Ymd");
		$filePointer = $result = false;
		
		if (!is_dir($_DEBUG_LOGPATH))
		{
			if (!@mkdir($_DEBUG_LOGPATH)) die("$_DEBUG_LOGPATH does not exists and is impossible to create");
		}
		$logFileName = "$_DEBUG_LOGPATH/$_DEBUG_APPNAME-$dateStamp.log";
		
		$filePointer = fopen($logFileName,"a");
		if (!$filePointer) die ("impossible d'ouvrir $logFileName !");
		
		$result = fwrite($filePointer,$this->__toString());
		if (!$result) die ("impossible d'écrire dans $logFileName !");
		
		fclose($filePointer);
		unset($filePointer);
		
		return true;
	}
	
	public function sendMail()
	{
		// Importation des variables de configuration
		global $_DEBUG_MAILTO, $_DEBUG_APPNAME, $_DEBUG_MAILFROM;
		
		$this->mailSubject = "[BUG] $_DEBUG_APPNAME [{$this->code}]"; // . $this->message;
		$headers = "From: " . $_DEBUG_MAILFROM . "\r\nX-Mailer: DEBUGGER \r\n" ;
		$this->mailMessage = $this->__toString();
		
		foreach ($_DEBUG_MAILTO as $to)
		{
			// mail($to,$this->mailSubject,$this->mailMessage,$headers);
			try
			{
				@mail($to,$this->mailSubject,$this->mailMessage,$headers);
			}
			catch (Exception $e)
			{
				// silence !
			}
		}
	}
	
	public function setFile($filename=false)
	{
		if ($filename)
		{
			$this->file = $filename;
		}
		
		return false;
	}
	
	public function setLine($line=false)
	{
		if ($filename)
		{
			return $this->line = $filename;
		}
		
		return false;
	}
	
	
	/**
	 * 
	 * netSocketSend()
	 * 
	 * Send a message via the netSocket
	 * 
	 */
	public function netSocketSend($message=false)
	{
		$this->netPacketCounter++;
		$chksum = md5($message);
		$now = date("Y/m/d - H:i:s");
		$finalMessage = "{$this->netPacketCounter} | $now | $message {$this->_CRLF}";
		$finalMessageLen = strlen($finalMessage);
		
		if ($message)
		{
			if (is_resource($this->netSocket))
			{
				if (!@socket_write($this->netSocket,$finalMessage))
				{
					// Something went wrong... let's figure out what happened...
					$this->netSocketLastErrorCode = @socket_last_error($this->netSocket);
					$this->netSocketLastErrorMessage = @socket_strerror($this->netSocketLastErrorCode);
					$this->netSocket = false;
					return false;
				}
			}
		}
		
		return false;
	}
	
	
	/**
	 * 
	 * netSocketSendReport()
	 * 
	 * Send a detailed error report via the netSocket
	 * 
	 */
	
	public function netSocketSendReport()
	{
		$report = '';
		return $this->netSocketSend($report);
	}

	/**
	 * 
	 * 
	 * 
	 */
	public function __toString()
	{
		//$separator = str_repeat('-',78);
		global $_DEBUG_VERBOSE;

		// Preparation contenu message d'erreur
		$header = "{$this->errorDate}@{$this->errorTime} [{$this->code}] {$this->message}";
		$detail = "Fichier : {$this->file} (ligne = {$this->line})" ;
		$client = $this->serializeAssociativeArray($this->client);
		$appel = parent::getTraceAsString();
		
		// Choix du niveau de bavardage
		switch ($_DEBUG_VERBOSE)
		{
			case 'V' :
			{
				return "$header\r\n\r\n";
			} break;
			
			case 'VV' :
			{
				return "$header\r\n$detail\r\n\r\n";
			} break;
			
			case 'VVV' :
			{
				return "$header\r\n$detail\r\n$appel\r\n$client\r\n\r\n";
			} break;
			
			default :
			{
				return '';
			}
		}
	}
	
	#########################################################################
	#
	# STATIC METHODS
	#
	#########################################################################

	/**
	*
	* debugVar()
	*
	* Affiche le contenu de la variable passee en argument de maniere humainement lisible
	*
	* @param mixed $variable Variable dont on souhaite observer le contenu
	* @return void
	*
	*/
	public static function debugVar($variable=false)
	{
		var_dump($variable);
	}

	
	/**
	*
	* debugVarHTML()
	*
	* Affiche le contenu de la variable passee en argument de maniere humainement lisible
	* dans une page web
	*
	* @param mixed $variable Variable dont on souhaite observer le contenu
	* @return void
	*
	*/
	public static function debugVarHTML($variable=false)
	{
		if (!$variable)
		{
			return false;
		}
		
		print '<hr />';
		print '<pre>';
		$this->debugVar($variable);
		print '</pre>';
		print '<hr />';
	}

	/**
	*
	* debugAndStop()
	*
	* Affiche le contenu de la variable passee en argument de maniere humainement lisible
	* et stoppe l'execution de l'application
	* 
	* @param mixed $variable Variable dont on souhaite observer le contenu
	* @return void
	*
	*/
	public static function debugAndStop($variable=false)
	{
		print '<pre>';
		var_dump($variable);
		print '</pre>';
		die();
	}
	
	/**
	 * 
	 */
	 public static function htmlDebug($variable=false)
	 {
	 	$cssStyle = "background-color:#fffab9;border-color:b3ae62;border-style:solid;border-width=1px;padding:3px;margin:3px;width:75%;text-align:left;vertical-align:top;z-index:1;position:absolute;";
	 	print ("\n<div id=\"debug\" class=\"debug\" style=\"$cssStyle\">");
	 	print("<h4>debug</h4>");
	 	print ("\n<pre>");
	 	var_dump($variable);
	 	print ("\n</pre>");
	  	print ("\n</div>");
	 }
	 
	
	#########################################################################
	#
	# METHODES PRIVEES
	#
	#########################################################################
	
	/**
	 * getClientInformations();
	 * 
	 */
	private function getClientInformations()
	{
		// Avoid "client" information if called from CLI.
		if (strstr(strtolower(php_sapi_name()),'cli')) return $this->client = array();
		$this->client['ip'] = $_SERVER['REMOTE_ADDR'];
		$this->client['portDistant'] = $_SERVER['REMOTE_PORT'];
		$this->client['portLocal'] = $_SERVER['SERVER_PORT'];
		$this->client['hostName'] = $_SERVER['REMOTE_HOST'];
		$this->client['requestMethod'] = $_SERVER['REQUEST_METHOD'];
		$this->client['queryString'] = $_SERVER['QUERY_STRING'];
		$this->client['httpAcceptCharset'] = $_SERVER['HTTP_ACCEPT_CHARSET'];
		$this->client['httpAcceptEncoding'] = $_SERVER['HTTP_ACCEPT_ENCODING'];
		$this->client['httpAcceptLanguage'] = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
		$this->client['httpConnection'] = $_SERVER['HTTP_CONNECTION'];
		$this->client['httpHost'] = $_SERVER['HTTP_HOST'];
		$this->client['httpReferer'] = $_SERVER['HTTP_REFERER'];
		$this->client['httpUserAgent'] = $_SERVER['HTTP_USER_AGENT'];
		$this->client['requestURI'] = $_SERVER['REQUEST_URI'];
	}
	
	/*
	 * serializeAssociativeArray()
	 */
	private function serializeAssociativeArray($array=false,$glue=';')
	{
		if ($array) return implode($glue,$array);
		return false;
	}

	/*
	 * setSysLog()
	 * 
	 */
	private function setSyslog($syslogLevel=LOG_INFO)
	{
		$arVerbose = explode("\n",$this->__toString());
		foreach($arVerbose as $line)
		{
			if (trim($line) != '')
			{
				$message = "debug : " . $line;
				@syslog($syslogLevel,$message);
			}
		}
		
		return true;
	}
	
	/**
	 * 
	 * netSocketCreate()
	 * 
	 */
	private function netSocketCreate()
	{
		global $_DEBUG_NETWORK_SOURCE_IP,
			   $_DEBUG_NETWORK_DESTINATION_IP,
			   $_DEBUG_NETWORK_SOURCE_PORT,
			   $_DEBUG_NETWORK_DESTINATION_PORT;
		
		// test if socket already exists
		if (!is_resource($this->netSocket))
		{
			// we create an non-blocking UDP socket (less constraints than TCP)
			// bind it to IP:PORT specified and "connect" it to the specified
			// server
			if($this->netSocket = @socket_create(AF_INET,SOCK_DGRAM,SOL_UDP))
			{
				if (@socket_bind($this->netSocket,$_DEBUG_NETWORK_SOURCE_IP,$_DEBUG_NETWORK_SOURCE_PORT))
				{
					if (@socket_connect($this->netSocket,$_DEBUG_NETWORK_DESTINATION_IP,$_DEBUG_NETWORK_DESTINATION_PORT))
					{
						if (@socket_set_nonblock($this->netSocket))
						{
							return true;
						}
					}
				}
			}
			
			// Something went wrong... let's figure out what happened...
			$this->netSocketLastErrorCode = @socket_last_error($this->netSocket);
			$this->netSocketLastErrorMessage = @socket_strerror($this->netSocketLastErrorCode);
			$this->netSocket = false;
			return false;
		}
		else // The socket seems to exist : verifying if it's true and
		     // set the socket to false and recursively call the method if
			 // it's not actually a socket
		{
			// socket test : if we get a positive response to the get_option
			// call, it's a socket, otherwise, it's not.
			if (!socket_get_option($this->netSocket,SOL_SOCKET,SO_TYPE))
			{
				// Destroying the "socket"
				$dumb = $this->netSocketDestroy();
				return $this->netSocketCreate();
			}
		}
		
		return true;
	}
	
	/**
	 * 
	 * netSocketDestroy
	 * 
	 * @param int $socket existing socket reference to close
	 */
	private function netSocketDestroy($socket=false)
	{
		$socket = (!$socket) ? $this->netSocket : $socket;
		while(!@socket_shutdown($socket,2))
		{
			// 
		}
		@socket_close($socket);
		$this->netSocket = false;
		return true;
	}
	
	
	/**
	 * 
	 * setLevel()
	 * 
	 */
	private function setLevel()
	{
		switch ($this->code)
		{
			case E_ERROR :
			{
				$this->level = 'ERROR';
			} break;

			case E_WARNING :
			{
				$this->level = 'WARNING';
			} break;

			case E_PARSE :
			{
				$this->level = 'PARSE';
			} break;

			case E_NOTICE :
			{
				$this->level = 'NOTICE';
			} break;

			case E_USER_ERROR :
			{
				$this->level = 'USER ERROR';
			} break;

			case E_USER_WARNING :
			{
				$this->level = 'USER WARNING';
			} break;

			case E_USER_NOTICE :
			{
				$this->level = 'USER NOTICE';
			} break;
			
			default :
			{
				$this->level = 'EXCEPTION';
			}
		}
	}
	
}

?>
